<template>
  <div class="containers">
    <div class="my-process-designer__header">
      <a-button-group key="file-control">
        <a-button  :type="headerButtonType" @click="uploadClick">打开文件</a-button>
        <a-dropdown>
          <a-button  :type="headerButtonType" >下载文件</a-button>
          <a-menu slot="overlay">
            <a-menu-item key="1" @click="downloadProcessAsXml"> 下载为XML文件 </a-menu-item>
            <a-menu-item key="2" @click="downloadProcessAsSvg"> 下载为SVG文件</a-menu-item>
            <a-menu-item key="3" @click="downloadProcessAsBpmn"> 下载为BPMN文件</a-menu-item>
          </a-menu>
        </a-dropdown>
        <a-button  :type="headerButtonType" @click="saveClick">保存</a-button>
      </a-button-group>
      <a-button-group key="scale-control">
        <a-tooltip title="缩小视图">
          <a-button  :disabled="zoom < 0.2" icon="zoom-out" @click="handleZoomIn" />
        </a-tooltip>
        <a-button >{{ Math.floor(this.zoom * 10 * 10) + "%" }}</a-button>
        <a-tooltip  title="放大视图">
          <a-button  :disabled="zoom > 4" icon="zoom-in" @click="handleZoomOut" />
        </a-tooltip>
        <a-tooltip title="重置视图并居中">
          <a-button  icon="environment" @click="handleReZoom" />
        </a-tooltip>
      </a-button-group>
      <a-button-group key="stack-control1">
        <a-tooltip title="撤销">
          <a-button  :disabled="!revocable" icon="undo" @click="handleUndo" />
        </a-tooltip>
        <a-tooltip title="恢复">
          <a-button  :disabled="!recoverable" icon="redo" @click="handleRedo" />
        </a-tooltip>
        <a-tooltip title="重新绘制">
          <a-button  icon="sync" @click="handleRestart" />
        </a-tooltip>
      </a-button-group>
      <a-button-group key="stack-control2">
        <a-dropdown>
          <a-button  icon="eye"></a-button>
          <a-menu slot="overlay">
            <a-menu-item key="1" @click="handleProcessXML"> 预览XML </a-menu-item>
            <!-- <a-menu-item key="2" > 预览JSON</a-menu-item> -->
          </a-menu>
        </a-dropdown>
        <a-tooltip :title="simulationStatus ? '退出模拟' : '开启模拟'">
          <a-button icon="bug" @click="handleSimulation" />
        </a-tooltip>
      </a-button-group>
    </div>
    <div class="canvas" ref="canvas"></div>
    <!-- <div id="js-properties-panel" class="panel"></div> -->
    <BpmnPanelAntdv ref="BpmnPanelAntdv" class="panel2" :options="options" />
    <!-- 用于打开本地文件-->
    <input type="file" id="files" ref="refFile" style="display: none" accept=".xml, .bpmn" @change="importLocalFile" />
    <a-modal
      class="bpmn-design-modal"
      title="预览"
      width="60%"
      v-drag
      :footer="null"
      :visible.sync="previewModelVisible"
      @cancel="() => this.previewModelVisible = false"
      destroyOnClose
      :bodyStyle="{height:'500px'}"
    >
      <codemirror v-model="bpmnStr" :options="cmOptions" />
    </a-modal>
  </div>
</template>

<script>

import BpmnModeler from 'bpmn-js/lib/Modeler'

import CamundaBpmnModdle from 'camunda-bpmn-moddle/resources/camunda.json'

import 'bpmn-js/dist/assets/diagram-js.css'
import 'bpmn-js/dist/assets/bpmn-js.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'

// 模拟流转流程
import TokenSimulationModule from 'bpmn-js-token-simulation'
import 'bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css'
// 汉化
import customTranslate from './workflow/customTranslate/customTranslate'
var customTranslateModule = {
  translate: ['value', customTranslate]
}

// 拓展
import UserTaskContextPad from './contextPad/userTaskContextPadProvider'

import { codemirror } from 'vue-codemirror'

// require styles
import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/abcdef.css'

// language
import 'codemirror/mode/xml/xml' // xml编辑器模式
import { BpmnPanelAntdv } from 'bpmn-panel-antdv/lib/bpmn-panel-antdv-mini.umd'
import 'bpmn-panel-antdv/lib/bpmn-panel-antdv-mini.css'
import Criteria from '@/utils/criteria'
import { getAction } from '@/services/manage'
import { OPERATOR } from '@/store/mutation-types'
import { filterObj } from '@/utils/util'
export default {
  name: 'bpmn',
  components: {
    codemirror,
    BpmnPanelAntdv
  },
  props: {
    xml: {
      type: String,
      default: ''
    },
    flow: {
      type: Object,
      default: () => {}
    }
  },
  data() {
    return {
      bpmnModeler: null, // bpmn建模器
      zoom: 1,
      shape: '',
      simulationStatus: false,
      recoverable: false,
      revocable: false,
      headerButtonSize: '',
      headerButtonType: 'primary',
      cmOptions: {
        tabSize: 4,
        mode: 'text/x-sql',
        theme: 'abcdef',
        styleActiveLine: true,
        lineNumbers: true,
        line: true,
        hintOptions: { // 加提示
          completeSingle: false
        },
        height: 500
      },
      bpmnStr: '',
      previewModelVisible: false,
      options: {
        // idAndNameDisabled: true, // 流程id和name禁用编辑
        assigneeApi: this.assigneeApi,
        candidateUsersApi: this.assigneeApi,
        candidateGroupsApi: this.candidateGroupsApi,
        formKeyApi: this.formKeyApi
      }
    }
  },
  mounted() {
    this.$nextTick(() => {
      this.initBpmn()
    })
  },
  methods: {
    initBpmn() {
      const canvas = this.$refs.canvas
      this.bpmnModeler = new BpmnModeler({ // 建模
        container: canvas,
        // propertiesPanel: {
        //   parent: '#js-properties-panel' // 添加控制板
        // },
        additionalModules: [
          // BpmnPropertiesPanelModule,
          // BpmnPropertiesProviderModule,
          // CamundaPlatformPropertiesProviderModule,
          customTranslateModule,
          TokenSimulationModule,
          UserTaskContextPad
        ],
        moddleExtensions: {
          camunda: CamundaBpmnModdle
        }
      })
      this.$refs.BpmnPanelAntdv.init(this.bpmnModeler)
      this.createNewDiagram(this.flow.xml)
    },
    async createNewDiagram(xml) {
      try {
        if (!xml) {
          await this.bpmnModeler.createDiagram()
          this.shape = this.bpmnModeler.get('canvas')._rootElement
          const modeling = this.bpmnModeler.get('modeling')
          if (this.shape) {
            modeling.updateProperties(this.shape, {
              isExecutable: true,
              id: this.flow.code,
              name: this.flow.name
            })
          }
        } else {
          await this.bpmnModeler.importXML(xml)
        }
        // 回写模板名称及添加事件
        this.success()
        // 图形自适应
        var canvas = this.bpmnModeler.get('canvas')
        canvas.zoom('fit-viewport')
      } catch (error) {
        console.warn(error)
      }
    },
    success() {
      this.addBpmnListener()
    },
    // 添加绑定事件
    addBpmnListener() {
      try {
        this.recoverable = this.bpmnModeler.get('commandStack').canRedo()
        this.revocable = this.bpmnModeler.get('commandStack').canUndo()
      } catch (e) {
        console.error(`[Process Designer Warn]: ${e.message || e}`)
      }
    },
    handleUndo() { // 后退
      this.bpmnModeler.get('commandStack').undo()
    },
    handleRedo() { // 前进
      this.bpmnModeler.get('commandStack').redo()
    },
    handleRestart() {
      this.recoverable = false
      this.revocable = false
      this.createNewDiagram(null).then(() => this.bpmnModeler.get('canvas').zoom(1, 'auto'))
    },
    handleZoomIn() {
      this.zoom += 0.1
      var canvas = this.bpmnModeler.get('canvas')
      canvas.zoom(this.zoom)
    },
    handleZoomOut() {
      this.zoom -= 0.1
      var canvas = this.bpmnModeler.get('canvas')
      canvas.zoom(this.zoom)
    },
    handleReZoom() {
      this.zoom = 1
      this.bpmnModeler.get('canvas').zoom('fit-viewport', 'auto')
    },
    saveClick() {
      this.getDiagramXml().then((data) => {
        this.$emit('ok', data)
      })
    },
    async getDiagramXml(done) {
      try {
        const result = await this.bpmnModeler.saveXML({ format: true })
        const result2 = await this.bpmnModeler.saveSVG()
        const { xml } = result
        const { svg } = result2
        return {
          xml,
          svg
        }
      } catch (err) {
        console.log(err)
      }
    },
    handleProcessXML() {
      this.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
        this.bpmnStr = xml
        this.cmOptions.mode = 'application/xml'
        this.previewModelVisible = true
      })
    },
    handleSimulation() {
      this.simulationStatus = !this.simulationStatus
      this.bpmnModeler.get('toggleMode').toggleMode()
    },
    uploadClick() {
      this.$refs.refFile.click()
    },
    // 加载本地文件
    importLocalFile() {
      const that = this
      const file = this.$refs.refFile.files[0]
      const reader = new FileReader()
      reader.readAsText(file)
      reader.onload = function() {
        const xmlStr = this.result
        that.createNewDiagram(xmlStr)
      }
    },
    downloadProcessAsXml() {
      this.downloadProcess('xml')
    },
    downloadProcessAsBpmn() {
      this.downloadProcess('bpmn')
    },
    downloadProcessAsSvg() {
      this.downloadProcess('svg')
    },
    // 下载流程图到本地
    async downloadProcess(type, name) {
      try {
        const _this = this
        // 按需要类型创建文件并下载
        if (type === 'xml' || type === 'bpmn') {
          const { err, xml } = await this.bpmnModeler.saveXML()
          // 读取异常时抛出异常
          if (err) {
            console.error(`[Process Designer Warn ]: ${err.message || err}`)
          }
          const { href, filename } = _this.setEncoded(type.toUpperCase(), name, xml)
          downloadFunc(href, filename)
        } else {
          const { err, svg } = await this.bpmnModeler.saveSVG()
          // 读取异常时抛出异常
          if (err) {
            return console.error(err)
          }
          const { href, filename } = _this.setEncoded('SVG', name, svg)
          downloadFunc(href, filename)
        }
      } catch (e) {
        console.error(`[Process Designer Warn ]: ${e.message || e}`)
      }
      // 文件下载方法
      function downloadFunc(href, filename) {
        if (href && filename) {
          const a = document.createElement('a')
          a.download = filename // 指定下载的文件名
          a.href = href //  URL对象
          a.click() // 模拟点击
          URL.revokeObjectURL(a.href) // 释放URL 对象
        }
      }
    },
    // 当图发生改变的时候会调用这个函数，这个data就是图的xml
    setEncoded(type, filename = 'diagram', data) {
      const encodedData = encodeURIComponent(data)
      return {
        filename: `${filename}.${type}`,
        href: `data:application/${type === 'svg' ? 'text/xml' : 'bpmn20-xml'};charset=UTF-8,${encodedData}`,
        data: data
      }
    },
    async assigneeApi(parms) {
      if (parms) {
        const params = this.getQueryParams(filterObj({
          username: parms.query?.value,
          nickname: parms.query?.label
        }), parms.page)
        const res = await getAction('/user/list', params)
        const data = []
        if (res.success) {
          res.result.content.forEach(item => {
            data.push({ label: item.nickname, value: item.username })
          })
        }
        return {
          total: res.result.totalElements,
          result: data
        }
      }
    },
    async candidateGroupsApi(parms) {
      if (parms) {
        const params = this.getQueryParams(filterObj({
          id: parms.query?.value,
          name: parms.query?.label
        }), parms.page)
        const res = await getAction('/role/page', params)
        const data = []
        if (res.success) {
          res.result.content.forEach(item => {
            data.push({ label: item.name, value: `ROLE_${item.id}` })
          })
        }
        return {
          total: res.result.totalElements,
          result: data
        }
      }
    },
    async formKeyApi(parms) {
      if (parms) {
        const params = this.getQueryParams(filterObj({
          lastDefKey: parms.query?.value,
          name: parms.query?.label
        }), parms.page)
        const res = await getAction('/camunda/form/page', params)
        const data = []
        if (res.success) {
          res.result.content.forEach(item => {
            data.push({ label: item.name, value: item.lastDefKey })
          })
        }
        return {
          total: res.result.totalElements,
          result: data
        }
      }
    },
    getQueryParams(parms, ipagination) { // 获取查询条件
      const queryParam = new Criteria()
      queryParam.addCriterions(parms, OPERATOR.LIKE)// 过滤参数转换为jpa支持格式
      var param = Object.assign({}, { criteria: queryParam.getEncode() })
      param.pageNo = ipagination.current
      param.pageSize = ipagination.pageSize
      return param
    }
  }
}
</script>
<style lang="less">
.containers .djs-palette.two-column.open{
  width:100px !important;
}
.bts-toggle-mode{
  display: none !important;
}
.my-process-designer__header {
  width: 100%;
  min-height: 36px;
  display: flex;
  .ant-btn-group {
    margin: 4px;
  }
  .align {
    position: relative;
    i {
      &:after {
        content: "|";
        position: absolute;
        transform: rotate(90deg) translate(200%, 60%);
      }
    }
  }
  .align.align-left i {
    transform: rotate(90deg);
  }
  .align.align-right i {
    transform: rotate(-90deg);
  }
  .align.align-top i {
    transform: rotate(180deg);
  }
  .align.align-bottom i {
    transform: rotate(0deg);
  }
  .align.align-center i {
    transform: rotate(90deg);
    &:after {
      transform: rotate(90deg) translate(0, 60%);
    }
  }
  .align.align-middle i {
    transform: rotate(0deg);
    &:after {
      transform: rotate(90deg) translate(0, 60%);
    }
  }
}
.bpmn-design-modal{
  .vue-codemirror,.CodeMirror{
    height:100%;
  }
}
</style>
<style scoped>
.canvas{
	position: absolute;
	left: 0;
	top: 100px;
  right: 0;
  bottom: 0;
}
.panel{
	position: absolute;
	right: 0;
	top: 55px;
  bottom: 0;
  width: 300px;
  overflow: auto;
}
.panel2{
	top: 55px;
  height: calc(100% - 55px);
}
.containers{
  width: 100%;
	height: calc(100vh - 52px);
  background-color: #ffffff;
  background: white;
  overflow: auto;
  background-image: linear-gradient(90deg, rgba(220, 220, 220, 0.5) 6%, transparent 0),
    linear-gradient(rgba(192, 192, 192, 0.5) 6%, transparent 0);
  background-size: 12px 12px;
  -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
}
</style>
